//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package webhook_serv

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"atomgit.com/openlinksaas/api-server/api_common"
	"atomgit.com/openlinksaas/api-server/dao"
	"atomgit.com/openlinksaas/api-server/dao/event_dao"
	"atomgit.com/openlinksaas/api-server/dao/project_dao"
	"atomgit.com/openlinksaas/api-server/dao/user_dao"
	"atomgit.com/openlinksaas/api-server/webhook_serv/webhook_serv_atomgit"
	"atomgit.com/openlinksaas/api-server/webhook_serv/webhook_serv_gitee"
	"atomgit.com/openlinksaas/api-server/webhook_serv/webhook_serv_gitlab"
	"atomgit.com/openlinksaas/api-server/webhook_serv/webhook_serv_harbor"
	"atomgit.com/openlinksaas/proto-gen-go.git/events_api"
	"atomgit.com/openlinksaas/proto-gen-go.git/external_events_api"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"
)

func Dispatch(projectId, eventSourceId string, header http.Header, payload []byte, logger *zap.Logger) error {
	eventSourceItem, err := event_dao.EventSourceDao.Get(context.Background(), projectId, eventSourceId)
	if err != nil {
		return err
	}
	logger.Info("recv webhook event", zap.String("projectId", projectId), zap.String("eventSourceId", eventSourceId), zap.String("title", eventSourceItem.Title))
	var eventStageItem *event_dao.EventStageDbItem
	var eventObj *anypb.Any
	if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_GITLAB) {
		eventStageItem, eventObj, err = webhook_serv_gitlab.Process(projectId, eventSourceId, header, payload, eventSourceItem.Secret, logger)
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_GITHUB) {
		//TODO
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_GITEA) {
		//TODO
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_GITEE) {
		eventStageItem, eventObj, err = webhook_serv_gitee.Process(projectId, eventSourceId, header, payload, eventSourceItem.Secret, logger)
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_GOGS) {
		//TODO
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_JIRA) {
		//TODO
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_CONFLUENCE) {
		//TODO
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_JENKINS) {
		//TODO
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_ATOMGIT) {
		eventStageItem, eventObj, err = webhook_serv_atomgit.Process(projectId, eventSourceId, header, payload, eventSourceItem.Secret, logger)
	} else if eventSourceItem.EventSource == int32(external_events_api.EVENT_SOURCE_EVENT_SOURCE_HARBOR) {
		eventStageItem, eventObj, err = webhook_serv_harbor.Process(projectId, eventSourceId, header, payload, eventSourceItem.Secret, logger)
	}
	if err != nil {
		return err
	}
	if eventStageItem == nil {
		logger.Error("unkwown event source", zap.String("projectId", projectId), zap.String("eventSourceId", eventSourceId), zap.String("title", eventSourceItem.Title))
		return fmt.Errorf("unkwown event source")
	}
	if eventStageItem.EventData == nil {
		eventStageItem.EventData, err = proto.Marshal(eventObj)
		if err != nil {
			return err
		}
	}

	dbSess, err := dao.StartSession()
	if err != nil {
		return err
	}
	defer dao.EndSession(dbSess)
	_, err = dbSess.WithTransaction(context.Background(), func(sessCtx mongo.SessionContext) (interface{}, error) {
		err = process(sessCtx, eventStageItem, eventObj, logger)
		if err != nil {
			logger.Error("process webhook failed", zap.String("projectId", projectId), zap.String("eventSourceId", eventSourceId),
				zap.String("title", eventSourceItem.Title), zap.Error(err))
		}
		err = event_dao.EventSourceStateDao.IncEventCount(sessCtx, eventStageItem.EventSourceId)
		if err != nil {
			logger.Error("inc event source event count failed", zap.String("projectId", projectId), zap.String("eventSourceId", eventSourceId),
				zap.String("title", eventSourceItem.Title), zap.Error(err))
		}
		return nil, err
	})
	return err
}

func process(ctx context.Context, eventStageItem *event_dao.EventStageDbItem, eventObj *anypb.Any, logger *zap.Logger) error {
	if eventStageItem.EventStageId == "" {
		eventStageItem.EventStageId = uuid.NewString()
	}
	if eventStageItem.SourceUserName == "" {
		return directAddEvent(ctx, eventStageItem, eventObj, logger)
	}
	//检查成员映射关系
	exist, err := event_dao.SourceUserDao.Exist(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		logger.Error("check event source user failed", zap.String("projectId", eventStageItem.ProjectId), zap.String("eventSourceId", eventStageItem.EventSourceId),
			zap.String("sourceUserName", eventStageItem.SourceUserName), zap.Error(err))
		return err
	}
	if !exist {
		return addSourceUserAndStage(ctx, eventStageItem, logger)
	}
	//读取映射关系
	sourceUserItem, err := event_dao.SourceUserDao.Get(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	if sourceUserItem.UserPolicy == int32(external_events_api.SOURCE_USER_POLICY_SOURCE_USER_POLICY_NONE) {
		return processPolicyNone(ctx, eventStageItem, logger)
	} else if sourceUserItem.UserPolicy == int32(external_events_api.SOURCE_USER_POLICY_SOURCE_USER_POLICY_DISCARD) {
		return nil
	} else if sourceUserItem.UserPolicy == int32(external_events_api.SOURCE_USER_POLICY_SOURCE_USER_POLICY_MAPPING) {
		return processPolicyMapping(ctx, eventStageItem, sourceUserItem.MapUserId, eventObj, logger)
	} else if sourceUserItem.UserPolicy == int32(external_events_api.SOURCE_USER_POLICY_SOURCE_USER_POLICY_SKIP_MAPPING) {
		return processPolicySkipMapping(ctx, eventStageItem, eventObj, logger)
	}
	return nil
}

func addSourceUserAndStage(ctx context.Context, eventStageItem *event_dao.EventStageDbItem, logger *zap.Logger) error {
	logger.Debug("add source user and stage event", zap.String("projectId", eventStageItem.ProjectId), zap.String("eventSourceId", eventStageItem.EventSourceId),
		zap.String("sourceUserName", eventStageItem.SourceUserName))
	nowTime := time.Now().UnixNano() / 1e6
	//创建默认映射关系
	err := event_dao.SourceUserDao.Insert(ctx, &event_dao.SourceUserInfoDbItem{
		SourceUserName:    eventStageItem.SourceUserName,
		SourceDisplayName: eventStageItem.SourceDisplayName,
		EventSourceId:     eventStageItem.EventSourceId,
		ProjectId:         eventStageItem.ProjectId,
		UserPolicy:        int32(external_events_api.SOURCE_USER_POLICY_SOURCE_USER_POLICY_NONE),
		MapUserId:         "",
		CreateTime:        nowTime,
		UpdateTime:        nowTime,
		TotalEventCount:   1,
	})
	if err != nil {
		return err
	}
	err = event_dao.EventSourceStateDao.SetUserCountDirty(ctx, eventStageItem.EventSourceId)
	if err != nil {
		return err
	}
	//写入stage
	err = event_dao.EventStageDao.Insert(ctx, eventStageItem)
	if err != nil {
		return err
	}
	err = event_dao.SourceUserDao.IncCount(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	return nil
}

func directAddEvent(ctx context.Context, eventStageItem *event_dao.EventStageDbItem, eventObj *anypb.Any, logger *zap.Logger) error {
	logger.Debug("direct add event", zap.String("projectId", eventStageItem.ProjectId), zap.String("eventSourceId", eventStageItem.EventSourceId))
	projectItem, err := project_dao.ProjectInfoDao.Get(ctx, eventStageItem.ProjectId)
	if err != nil {
		return err
	}
	err = event_dao.SourceUserDao.IncCount(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	err = api_common.AddEvent(ctx, &events_api.Event{
		EventId:         uuid.NewString(),
		UserId:          "",
		UserDisplayName: "",
		ProjectId:       eventStageItem.ProjectId,
		ProjectName:     projectItem.BasicInfo.ProjectName,
		EventType:       events_api.EVENT_TYPE(eventStageItem.EventType),
		EventTime:       eventStageItem.EventTime,
		RefType:         events_api.EVENT_REF_TYPE_EVENT_REF_TYPE_NONE,
		RefId:           "",
		EventData:       eventObj,
	}, false, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	return err
}

func processPolicyNone(ctx context.Context, eventStageItem *event_dao.EventStageDbItem, logger *zap.Logger) error {
	logger.Debug("process none policy for event", zap.String("projectId", eventStageItem.ProjectId), zap.String("eventSourceId", eventStageItem.EventSourceId),
		zap.String("sourceUserName", eventStageItem.SourceUserName))
	//写入stage
	err := event_dao.EventStageDao.Insert(ctx, eventStageItem)
	if err != nil {
		return err
	}
	//增加消息计数
	err = event_dao.SourceUserDao.IncCount(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	return nil
}

func processPolicyMapping(ctx context.Context, eventStageItem *event_dao.EventStageDbItem, mapUserId string, eventObj *anypb.Any, logger *zap.Logger) error {
	logger.Debug("process maping for event", zap.String("projectId", eventStageItem.ProjectId), zap.String("eventSourceId", eventStageItem.EventSourceId),
		zap.String("sourceUserName", eventStageItem.SourceUserName))
	exist, err := project_dao.MemberInfoDao.Exist(ctx, eventStageItem.ProjectId, mapUserId)
	if err != nil {
		return err
	}
	if !exist {
		return nil //discard
	}
	userItem, err := user_dao.UserInfoDao.Get(ctx, mapUserId)
	if err != nil {
		return err
	}
	projectItem, err := project_dao.ProjectInfoDao.Get(ctx, eventStageItem.ProjectId)
	if err != nil {
		return err
	}
	err = api_common.AddEvent(ctx, &events_api.Event{
		EventId:         uuid.NewString(),
		UserId:          mapUserId,
		UserDisplayName: userItem.BasicInfo.DisplayName,
		ProjectId:       eventStageItem.ProjectId,
		ProjectName:     projectItem.BasicInfo.ProjectName,
		EventType:       events_api.EVENT_TYPE(eventStageItem.EventType),
		EventTime:       eventStageItem.EventTime,
		RefType:         events_api.EVENT_REF_TYPE_EVENT_REF_TYPE_NONE,
		RefId:           "",
		EventData:       eventObj,
	}, false, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	//增加消息计数
	err = event_dao.SourceUserDao.IncCount(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	return nil
}

func processPolicySkipMapping(ctx context.Context, eventStageItem *event_dao.EventStageDbItem, eventObj *anypb.Any, logger *zap.Logger) error {
	logger.Debug("skip maping for event", zap.String("projectId", eventStageItem.ProjectId), zap.String("eventSourceId", eventStageItem.EventSourceId),
		zap.String("sourceUserName", eventStageItem.SourceUserName))
	projectItem, err := project_dao.ProjectInfoDao.Get(ctx, eventStageItem.ProjectId)
	if err != nil {
		return err
	}
	err = api_common.AddEvent(ctx, &events_api.Event{
		EventId:         uuid.NewString(),
		UserId:          "",
		UserDisplayName: "",
		ProjectId:       eventStageItem.ProjectId,
		ProjectName:     projectItem.BasicInfo.ProjectName,
		EventType:       events_api.EVENT_TYPE(eventStageItem.EventType),
		EventTime:       eventStageItem.EventTime,
		RefType:         events_api.EVENT_REF_TYPE_EVENT_REF_TYPE_NONE,
		RefId:           "",
		EventData:       eventObj,
	}, false, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	//增加消息计数
	err = event_dao.SourceUserDao.IncCount(ctx, eventStageItem.ProjectId, eventStageItem.EventSourceId, eventStageItem.SourceUserName)
	if err != nil {
		return err
	}
	return nil
}
